﻿using System;
using System.Collections.Generic;
using System.Linq;
using Myotragus.Data.Criteria;
using Myotragus.Data.Domain;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.SqlCommand;
using NHibernate.Transform;

namespace Myotragus.Data.Util
{
  public static class LocalizableCriteriaHelper
  {
    public static Type ResolveLoadEntityImplementation<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return ResolveEntityDescriptor(@this).LoadEntityImplementation;
    }

    public static Type ResolveSaveEntityImplementation<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return ResolveEntityDescriptor(@this).SaveEntityImplementation;
    }

    public static TAlias ResolveAlias<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.Services.Resolve<TAlias>();
    }

    public static TProjector ResolveProjector<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.Services.Resolve<TProjector>();
    }

    public static TDescriptor ResolveEntityDescriptor<TEntity, TDescriptor>(ILocalizableRepository<TEntity, TDescriptor> @this) 
      where TEntity : IEntity 
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
    {
      return @this.Services.Resolve<TDescriptor>();
    }

    public static void AddMatchingCriteria<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      ICriteria criteria, object pattern)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      var descriptor = ResolveEntityDescriptor(@this);
      foreach (var prop in ReflectionHelper.GetProperties(pattern))
      {
        if (!descriptor.EntityProperties.ContainsKey(prop.Name))
          continue;

        var value = prop.GetValue(pattern, ReflectionHelper.EmptyObjects);
        var property = descriptor.LocalizableProperties.Contains(prop.Name)
          ? Property.ForName(String.Format("{0}.Value", prop.Name))
          : Property.ForName(prop.Name);

        criteria.Add(property.Eq(value));
      }
    }

    public static void AddLikeCriteria<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      ICriteria criteria, object pattern)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      var descriptor = ResolveEntityDescriptor(@this);
      foreach (var prop in ReflectionHelper.GetProperties(pattern))
      {
        if (!descriptor.EntityProperties.ContainsKey(prop.Name))
          continue;

        var value = prop.GetValue(pattern, ReflectionHelper.EmptyObjects);
        var property = descriptor.LocalizableProperties.Contains(prop.Name)
          ? Property.ForName(String.Format("{0}.Value", prop.Name))
          : Property.ForName(prop.Name);

        criteria.Add(prop.PropertyType == ReflectionHelper.TypeOfString
          ? property.Like(value.ToString(), MatchMode.Anywhere)
          : property.Eq(value));
      }
    }

    public static void SetResultTransformer<TEntity, TDescriptor, TProjector, TAlias>(
        ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
        ICriteria criteria)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      SetResultTransformer(criteria, @this.EntityImplementation);
    }
    
    public static void SetResultTransformer(ICriteria criteria, Type entityImplementation)
    {
      criteria.SetResultTransformer(Transformers.AliasToBean(entityImplementation));
      return;
    }

    public static void SetProjection<TEntity, TDescriptor, TProjector, TAlias>(
        ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
        ICriteria criteria, string path)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      SetProjection(criteria, ResolveProjector(@this), path);
    }
    
    public static void SetProjection(ICriteria criteria, IProjector projector, string path)
    {
      criteria.SetProjection(projector.GetProjection(path));
    }

    public static void AddAliases<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      ICriteria criteria, int lcId)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      var descriptor = ResolveEntityDescriptor(@this);
      var alias = ResolveAlias(@this);
      AddAliases(criteria, alias, lcId, descriptor.LocalizableProperties);
    }
    public static void AddAliases<TEntity, TDescriptor, TProjector, TAlias>(
      ILocalizableReadRepository<TEntity, TProjector, TAlias> @this,
      ICriteria criteria, string path, int lcId)
      where TEntity : IEntity
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      var descriptor = ResolveEntityDescriptor(@this);
      var alias = ResolveAlias(@this);
      AddAliases(criteria, alias, path, lcId, descriptor.LocalizableProperties);
    }

    public static void AddAliases(ICriteria criteria, ILocalizableAlias alias, int lcId, 
                                   IEnumerable<string> properties)
    {
      foreach (var prop in properties)
        alias.CreateAlias(criteria, "", prop, JoinType.LeftOuterJoin, lcId);
    }

    public static void AddAliases(ICriteria criteria, ILocalizableAlias alias, string path, int lcId,
                                   IEnumerable<string> properties)
    {
      foreach (var prop in properties)
        alias.CreateAlias(criteria, path, prop, JoinType.LeftOuterJoin, lcId);
    }
  }
}
